﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
using nHydrate.GeoLocation.Interfaces;
using Widgetsphere.Autos.EFDAL;

namespace Widgetsphere.Autos.API
{
    public static class GeoLocationHelper
    {
        public static int GetStateId(string name)
        {
            try
            {
                using (var context = new AutosEntities())
                {
                    return context.State
                        .Where(x => x.Name == name || x.Abbr == name)
                        .Select(x => x.StateId)
                        .FirstOrDefault();
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        public static ZipInfo GetZipInfo(string zip)
        {
            try
            {
                using (var factory = GetFactory())
                {
                    var service = factory.CreateChannel();
                    return service.GetZip(zip);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "ZIP: " + zip);
                return null;
            }
        }

        public static ZipInfo GetCityState(string city, string state)
        {
            try
            {
                using (var factory = GetFactory())
                {
                    var service = factory.CreateChannel();
                    return service.GetZipFromCityState(city, state);
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        public static List<string> GetLookup(string term)
        {
            try
            {
                using (var factory = GetFactory())
                {
                    var service = factory.CreateChannel();
                    return service.GetLookup(term);
                }
            }
            catch (System.Exception ex)
            {
                Logger.LogError(ex);
                return new List<string>();
            }
        }

        #region Private Helpers

        private static System.ServiceModel.ChannelFactory<nHydrate.GeoLocation.Interfaces.IGeoLocationService> GetFactory()
        {
            var myBinding = new BasicHttpBinding();
            myBinding.MaxBufferPoolSize = 2147483647;
            myBinding.MaxBufferSize = 2147483647;
            myBinding.MaxReceivedMessageSize = 2147483647;
            myBinding.ReaderQuotas.MaxDepth = 2147483647;
            myBinding.ReaderQuotas.MaxStringContentLength = 2147483647;
            myBinding.ReaderQuotas.MaxArrayLength = 2147483647;
            myBinding.ReaderQuotas.MaxBytesPerRead = 2147483647;
            myBinding.ReaderQuotas.MaxNameTableCharCount = 2147483647;
            var endpoint = new EndpointAddress(ConfigHelper.LocationServiceUrl);
            return new System.ServiceModel.ChannelFactory<nHydrate.GeoLocation.Interfaces.IGeoLocationService>(myBinding, endpoint);
        }

        #endregion

        private static Random _rnd = new Random();
        public static ZipInfo OffsetBy(this ZipInfo z, int miles)
        {
            const double CONVERT = 110;
            if (z == null) return z;
            if (miles <= 0) return z;
            var retval = new ZipInfo { Latitude = z.Latitude, Longitude = z.Longitude };
            var r1 = ((_rnd.Next(0, miles * 100) * 1.0) / CONVERT) / 100.0;
            var r2 = ((_rnd.Next(0, miles * 100) * 1.0) / CONVERT) / 100.0;
            retval.Latitude += r1;
            retval.Longitude += r2;
            return retval;
        }

        public static double DistanceFrom(this ZipInfo source, ZipInfo target)
        {
            /*
                The Haversine formula according to Dr. Math.
                http://mathforum.org/library/drmath/view/51879.html
                
                dlon = lon2 - lon1
                dlat = lat2 - lat1
                a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
                c = 2 * atan2(sqrt(a), sqrt(1-a)) 
                d = R * c
                
                Where
                    * dlon is the change in longitude
                    * dlat is the change in latitude
                    * c is the great circle distance in Radians.
                    * R is the radius of a spherical Earth.
                    * The locations of the two points in 
                        spherical coordinates (longitude and 
                        latitude) are lon1,lat1 and lon2, lat2.
            */
            double dDistance = Double.MinValue;
            double dLat1InRad = source.Latitude * (Math.PI / 180.0);
            double dLong1InRad = source.Longitude * (Math.PI / 180.0);
            double dLat2InRad = target.Latitude * (Math.PI / 180.0);
            double dLong2InRad = target.Longitude * (Math.PI / 180.0);

            double dLongitude = dLong2InRad - dLong1InRad;
            double dLatitude = dLat2InRad - dLat1InRad;

            // Intermediate result a.
            double a = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) +
                       Math.Cos(dLat1InRad) * Math.Cos(dLat2InRad) *
                       Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);

            // Intermediate result c (great circle distance in Radians).
            double c = 2.0 * Math.Asin(Math.Sqrt(a));

            // Distance.
            // const Double kEarthRadiusMiles = 3956.0;
            const Double kEarthRadiusKms = 6376.5;
            dDistance = kEarthRadiusKms * c;

            return dDistance;
        }

    }
}